package com.gmstu.springframework.context;

import com.gmstu.mvcservlet.annotation.GpAutowired;
import com.gmstu.mvcservlet.annotation.GpController;
import com.gmstu.mvcservlet.annotation.GpService;
import com.gmstu.springframework.aop.GpAopProxy;
import com.gmstu.springframework.aop.GpCglibAopProxy;
import com.gmstu.springframework.aop.GpJdkDynamicAopProxy;
import com.gmstu.springframework.aop.config.GpAopConfig;
import com.gmstu.springframework.aop.support.GpAdvisedSupport;
import com.gmstu.springframework.beans.GPBeanWrapper;
import com.gmstu.springframework.beans.config.GPBeanPostProcessor;
import com.gmstu.springframework.beans.config.GpBeanDefinition;
import com.gmstu.springframework.beans.support.GPBeanDefinitionReader;
import com.gmstu.springframework.context.support.GpAbstractApplicationContext;
import com.gmstu.springframework.core.GpBeanFactory;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author John.zhang
 * @version Id: GpApplicationContext, v 1.0 2020/8/20 11:53 ZhiYong Exp $
 */
public class GpApplicationContext extends GpAbstractApplicationContext implements GpBeanFactory {

    private String[] configLocations;

    private GPBeanDefinitionReader beanDefinitionReader;

    private Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>();

    private Map<String, GPBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, GPBeanWrapper>();

    public GpApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void refresh() throws Exception {
        beanDefinitionReader = new GPBeanDefinitionReader(this.configLocations);
        List<GpBeanDefinition> beanDefinitions = beanDefinitionReader.loadBeanDefinitions();

        doRegisterBeanDefinition(beanDefinitions);

        doAutoWrite();
    }

    private void doAutoWrite() {
        for (Map.Entry<String, GpBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            if (!beanDefinitionEntry.getValue().isLazyInit()) {
                getBean(beanName);
            }
        }
    }

    private void doRegisterBeanDefinition(List<GpBeanDefinition> beanDefinitions) throws Exception {

        for (GpBeanDefinition beanDefinition : beanDefinitions) {
            if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("");
            }
            super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
    }


    @Override
    public Object getBean(Class<?> beanClass) {
        return getBean(beanClass.getName());
    }

    @Override
    public Object getBean(String beanName) {
        GpBeanDefinition beanDefinition = super.beanDefinitionMap.get(beanName);
        Object instance = null;

        //参考spring源码 AbstractAutowireCapableBeanFactory
        GPBeanPostProcessor beanPostProcessor = new GPBeanPostProcessor();
        beanPostProcessor.postProcessBeforeInitialization(instance, beanName);

        instance = instantiateBean(beanName, beanDefinition);

        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);
        factoryBeanInstanceCache.put(beanName, beanWrapper);

        beanPostProcessor.postProcessAfterInitialization(instance, beanName);

        populateBean(beanName, new GpBeanDefinition(), beanWrapper);

        return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    }


    private void populateBean(String beanName, GpBeanDefinition beanDefinition, GPBeanWrapper beanWrapper) {

        Class<?> clazz = beanWrapper.getWrappedClass();
        if (!(clazz.isAnnotationPresent(GpController.class) || clazz.isAnnotationPresent(GpService.class))) {
            return;
        }
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(GpAutowired.class)) {
                continue;
            }
            GpAutowired autowired = field.getAnnotation(GpAutowired.class);
            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }
            field.setAccessible(true);
            Object wrappedInstance = beanWrapper.getWrappedInstance();
            try {
                if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
                    continue;
                }
                field.set(wrappedInstance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    private Object instantiateBean(String beanName, GpBeanDefinition beanDefinition) {
        String className = beanDefinition.getBeanClassName();
        Object instance = null;
        try {
            if (this.singletonObjects.containsKey(className)) {
                instance = this.singletonObjects.get(className);
            } else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();

                //Aop
                GpAdvisedSupport config = instantiateAopConfig();
                config.setTarget(instance);
                config.setTargetClass(clazz);
                if(config.pointCutMatch()){
                    createProxy(config).getProxy();
                }

                this.singletonObjects.put(className, instance);
                this.singletonObjects.put(beanDefinition.getFactoryBeanName(), instance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    private GpAopProxy createProxy(GpAdvisedSupport config) {
        Class<?> targetClass = config.getTargetClass();
        if(targetClass.getInterfaces().length>0){
            return new GpJdkDynamicAopProxy(config);
        }
        return new GpCglibAopProxy(config);
    }

    private GpAdvisedSupport instantiateAopConfig() {
        GpAopConfig config = new GpAopConfig();
        Properties properties = this.beanDefinitionReader.getConfig();
        config.setPointCut(properties.getProperty("pointCut"));
        config.setPointCut(properties.getProperty("aspectClass"));
        config.setPointCut(properties.getProperty("aspectBefore"));
        config.setPointCut(properties.getProperty("aspectAfter"));
        config.setPointCut(properties.getProperty("aspectAfterThrow"));
        config.setPointCut(properties.getProperty("aspectAfterThrowingName"));
        return new GpAdvisedSupport(config);
    }


    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig() {
        return this.beanDefinitionReader.getConfig();
    }

}
